//////////////////////////////////////////////////////////////////
// MattiseOS Kernel
// Date: 2008
// Author: Matthew Iselin
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//////////////////////////////////////////////////////////////////

// Includes
#include <mattise.h>
#include <stdint.h>
#include <string.h>
#include <mem/util.h>
#include <mem/page.h>
#include <kernio/io.h>
#include <process/common.h>

// Prototypes

// reads the current EIP into a variable
uint32_t readeip();

// Functions

// forks a process (ie, creates a process with the exact same setup in a new address space)
uint32_t fork()
{
	// get the cpu flags
	uint32_t int_state = 0;
	asm volatile( "pushf; popl %0" : "=r" (int_state) );

	// clear the interrupt flag - we're messing around with process states here
	asm volatile( "cli" );

	// grab the current PID
	//uint32_t currid = getpid();

	// grab the CR3 of the current process
	uint32_t currcr3 = currprocess->cr3;

	// grab a new page directory
	uint32_t newcr3 = (uint32_t) kAllocatePages( 1 );

	// create a new process to contain the new copy
	uint32_t newid = 0;
	// int32_t procid = 0;
	kCreateProcess( 1, TF_CTXONLY, newcr3, 10, &newid, 0 );

	// get the entry
	static struct pentry* ent;
	for( ent = p_readyq; ent != 0; ent = ent->next )
	{
		if( (ent->me) && (newid == ent->me->id) )
			break;
	}

	// verify it
	if( ent == 0 )
		return 0;

	// grab the current eip
	uint32_t curreip = readeip();

	// check if we are the parent
	if( currprocess != ent->me )
	{
		// the esp/ebp for the new task
		uint32_t ebp = 0, esp = 0;

		// grab the ESP/EBP pair
		asm volatile( "mov %%esp,%0;" : "=r" (esp) );
		asm volatile( "mov %%ebp,%0" : "=r" (ebp) );

		// clone the directory now that we have stuff on the stack
		CloneDirectory( currcr3, newcr3 );

		// set the process id
		ent->me->id = newid;

		// set the new ESP/EBP
		ent->me->esp = esp;
		ent->me->ebp = ebp;
		ent->me->eip = curreip;

		// copy the heap information
		ent->me->heap_base = currprocess->heap_base;
		ent->me->heap_curr = currprocess->heap_curr;

		// copy all descriptors
		memcpy( ent->me->filedescs, currprocess->filedescs, 512 * sizeof( struct fd ) );

		// but make sure that they reference *THIS PROCESS*, not the caller
		int z;
		for( z = 0; z < 512; z++ )
			if( !ent->me->filedescs[z].avail )
				ent->me->filedescs[z].pid = ent->me->id;

		// copy the argv/argc
		ent->me->argv = currprocess->argv;
		ent->me->argc = currprocess->argc;

		// copy the environment
		ent->me->env = currprocess->env;

		// copy the current working directory
		strcpy( ent->me->currdir, currprocess->currdir );

		// setup the rest of the context state
		ent->me->cr3 = newcr3;
		ent->me->status = PSTATE_READY;
		currprocess->childid = newid;
		currprocess->numchildren++; // if it was 0, it's now 1

		dprintf("[TASK ] fork is returning %d\n", newid);

		// restore interrupts, if needed
		if( int_state & 0x200 )
			asm volatile( "sti" );

		// return the new task id
		return newid;
	}
	else
	{
		dprintf("[TASK ] forked child is returning %d\n", 0);

		// restore interrupts, if needed
		if( int_state & 0x200 )
			asm volatile( "sti" );

		// the child task
		return 0;
	}
}
